﻿/// <reference path="../Libraries/jasmine/jasmine.d.ts" />
/// <reference path="../Jasmine.ts" />

interface Window {
    anything: any;
}

declare module jasmine {
    interface Matchers {
        toBeASpy();
        toBeTypeOf(expected: any);
    }
}

describe('ExtSpec.Jasmine', function () {
    'use strict';

    var esj = ExtSpec.Jasmine,
        matchers = {
            toBeASpy: function () {
                return {
                    compare: function (actual) {
                        return {
                            pass: actual &&
                            ExtSpec.isTypeOf(actual, 'fn') &&
                            jasmine.isSpy(actual)
                        };
                    }
                };
            },

            toBeTypeOf: function (expected) {
                return {
                    compare: function (actual, expected) {
                        return {
                            pass: ExtSpec.isTypeOf(actual, expected)
                        };
                    }
                };
            }
        };

    beforeEach(function () {
        jasmine.addMatchers(matchers);
    });

    describe('createModelSpies', function () {
        it('should create all model spies given only an instance', function () {
            var instance: any = {
                    models: ['first.one', 'second.two']
                };

            esj.createModelSpies(instance);

            expect(instance.getFirstOneModel).toBeASpy();
            expect(instance.getSecondTwoModel).toBeASpy();
        });

        it('should create specified model spies on the given instance', function () {
            var instance: any = {
                    models: ['one@first', 'second', 'third']
                };

            esj.createModelSpies(instance, ['one', 'third']);

            expect(instance.getOneModel).toBeASpy();
            expect(instance.getSecondModel).not.toBeASpy();
            expect(instance.getThirdModel).toBeASpy();
        });

        it('should throw given a model not in the given instance', function () {
            var instance = {
                    models: ['first', 'second', 'third']
                };

            expect(function () {
                esj.createModelSpies(instance, ['fourth']);
            }).toThrowError('The models property for this class does not contain the item "fourth".');
        });
    });

    describe('createModelSpy', function () {
        var instance: any;

        beforeEach(function () {
            instance = {
                models: ['My@some.namespace']
            };
        });

        it('should create getMyModel given "My@some.namespace"', function () {
            esj.createModelSpy(instance, 'My@some.namespace');

            expect(instance.getMyModel).toBeTruthy();
        });

        it('should not throw given "My"', function () {
            expect(function () {
                esj.createModelSpy(instance, 'My');
            }).not.toThrow();
        });
    })

    describe('createRefSpy', function () {
        it('should create ref getter', function () {
            var instance: any = {
                    refs: [{
                        ref: 'someGrid',
                        selector: '#someGrid'
                    }]
                };

            esj.createRefSpy(instance, 'someGrid');

            expect(instance.getSomeGrid).toBeASpy();
        });


        it('should throw given ref that matches existing instance property', function () {
            var instance = {
                    getSomeGrid: false,
                    refs: [{
                        ref: 'someGrid',
                        selector: '#someGrid'
                    }]
                };

            expect(function () {
                esj.createRefSpy(instance, 'someGrid');
            }).toThrowError('An accessor spy for "someGrid" cannot be created because "getSomeGrid" already exists.');
        });

        it('should throw given ref that matches existing prototype property', function () {
            function Constructor() { }

            Constructor.prototype.getSomeGrid = false;
            Constructor.prototype.refs = [{
                ref: 'someGrid',
                selector: '#someGrid'
            }];

            var instance = new Constructor();

            expect(function () {
                esj.createRefSpy(instance, 'someGrid');
            }).toThrowError('An accessor spy for "someGrid" cannot be created because "getSomeGrid" already exists.');
        });
    });

    describe('createConfigSpies', function () {
        it('shoud exist', function () {
            expect(ExtSpec.Jasmine.createConfigSpies).not.toBeUndefined();
        });

        it('should add "getMyConfig" spy given instance with "myConfig"', function () {
            var instance: any = {
                    config: {
                        myConfig: true
                    }
                };

            esj.createConfigSpies(instance);

            expect(instance.getMyConfig).toBeASpy();
        });

        it('should add two getter spies given instance with two configs', function () {
            var instance: any = {
                    config: {
                        config1: true,
                        config2: false
                    }
                };

            esj.createConfigSpies(instance);

            expect(instance.getConfig1).toBeASpy();
            expect(instance.getConfig2).toBeASpy();
        });

        it('should add two setter spies given instance with two configs', function () {
            var instance: any = {
                    config: {
                        configA: true,
                        configB: false
                    }
                };

            esj.createConfigSpies(instance);

            expect(instance.setConfigA).toBeASpy();
            expect(instance.setConfigB).toBeASpy();
        });

        it('should add two getter spies given instance with four configs and a list of two', function () {
            var instance: any = {
                    config: {
                        config1: true,
                        config2: false,
                        config3: true,
                        config4: 434
                    }
                };

            esj.createConfigSpies(instance, ['config1', 'config3']);

            expect(instance.getConfig1).toBeASpy();
            expect(instance.getConfig2).not.toBeASpy();
            expect(instance.getConfig3).toBeASpy();
            expect(instance.getConfig4).not.toBeASpy();
        });

        it('should add two setter spies given instance with four configs and a list of two', function () {
            var instance: any = {
                    config: {
                        configA: true,
                        configB: false,
                        configC: true,
                        configD: 434
                    }
                };

            esj.createConfigSpies(instance, ['configA', 'configC']);

            expect(instance.setConfigA).toBeASpy();
            expect(instance.setConfigB).not.toBeASpy();
            expect(instance.setConfigC).toBeASpy();
            expect(instance.setConfigD).not.toBeASpy();
        });

        it('should throw given missing config', function () {
            var instance: any = {
                    config: {
                        config1: true,
                        config2: false
                    }
                };

            expect(function () {
                esj.createConfigSpies(instance, ['configA', 'config2']);
            }).toThrowError('The config property for this class does not contain the item "configA".');
        });
    });

    describe('createConfigSpy', function () {
        it('should exist', function () {
            expect(ExtSpec.Jasmine.createConfigSpy).not.toBeUndefined();
        });

        it('should add "getMyConfig" spy given "myConfig"', function () {
            var instance: any = {
                    config: {
                        myConfig: true
                    }
                };

            esj.createConfigSpy(instance, 'myConfig');

            expect(instance.getMyConfig).toBeASpy();
        });

        it('should add "setMyConfig" spy given "myConfig"', function () {
            var instance: any = {
                    config: {
                        myConfig: true
                    }
                };

            esj.createConfigSpy(instance, 'myConfig');

            expect(instance.setMyConfig).toBeASpy();
        });

        it('should add spy configuration to getter', function () {
            var actual,
                instance: any = {
                    config: {
                        myConfig: true
                    }
                };

            esj.createConfigSpy(instance, 'myConfig', ['subMethod']);

            actual = instance.getMyConfig.and.exec();

            expect(actual.subMethod).toBeASpy();
        });

        it('should throw given an unknown config name', function () {
            expect(function () {
                esj.createConfigSpy({}, 'myConfig');
            }).toThrowError('The config property for this class does not contain the item "myConfig".');
        });
    });

    describe('spyOnObject', function () {
        it('should create spies using array of strings', function () {
            var target: any = {};

            esj.spyOnObject(target, ['asdf', '3322', 'getSomething']);

            expect(target.asdf).toBeASpy();
            expect(target['3322']).toBeASpy();
            expect(target.getSomething).toBeASpy();
        });

        it('should create spy using config with name only', function () {
            var target: any = {};

            esj.spyOnObject(target, [{
                name: 'addCls'
            }]);

            expect(target.addCls).toBeASpy();
        });

        it('should automatically detect and create a fake spy when passed a function value', function () {
            var target: any = {},
                fake = jasmine.createSpy('fake');

            esj.spyOnObject(target, [{
                name: 'getEl',
                value: fake
            }]);

            target.getEl.and.exec()

            expect(fake).toHaveBeenCalled();
        });

        it('should automatically detect and create a returning spy object when passed an array value', function () {
            var target: any = {},
                actual;

            esj.spyOnObject(target, [{
                name: 'showAll',
                value: ['method1', 'method2', 'method3']
            }]);

            actual = target.showAll.and.exec();

            expect(target.showAll).toBeASpy();
            expect(actual.method1).toBeASpy();
            expect(actual.method2).toBeASpy();
            expect(actual.method3).toBeASpy();
        });

        it('should create a return spy when passed an array value and an explicit "return" action', function () {
            var target: any = {},
                returns = ['method1', 'method2', 'method3'];

            esj.spyOnObject(target, [{
                name: 'getter',
                value: returns,
                action: 'return'
            }]);

            expect(target.getter).toBeASpy();
            expect(target.getter.and.exec()).toBe(returns);
        });

        it('should create spies using three level deep config', function () {
            var target: any = {},
                getEl,
                addCls,
                hasValue;

            esj.spyOnObject(target, [
                'asdf',
                '3322',
                {
                    name: 'getGone',
                    value: <any[]>[
                        {
                            name: 'addCls',
                            value: 23
                        },
                        {
                            name: 'getEl',
                            value: ['asdf1', 'asdf2', 'asdf3']
                        }
                    ]
                },
                {
                    name: 'sub',
                    value: [{
                        name: 'hasValue',
                        value: true
                    }]
                },
                'asdf2'
            ]);

            getEl = target.getGone.and.exec().getEl.and.exec();
            addCls = target.getGone.and.exec().addCls.and.exec();
            hasValue = target.sub.and.exec().hasValue.and.exec();

            expect(target.asdf).toBeASpy();
            expect(target['3322']).toBeASpy();
            expect(target.getGone).toBeASpy();
            expect(addCls).toBe(23);
            expect(hasValue).toBe(true);
            expect(getEl.asdf1).toBeASpy();
            expect(getEl.asdf2).toBeASpy();
            expect(getEl.asdf3).toBeASpy();
            expect(target.asdf2).toBeASpy();
        });
    });

    describe('spyOnNamespace', function () {
        it('should attach spies to the namespace target', function () {
            esj.spyOnNamespace('anything', ['getTarget', 'setTarget']);

            expect(window.anything.getTarget).toBeASpy();
            expect(window.anything.setTarget).toBeASpy();
        });
    });

    describe('createSpyObject', function () {
        it('should add spy object property to created object', function () {
            var actual = esj.createSpyObject([{
                    name: 'testMethod',
                    value: [{
                        name: 'objectProperty',
                        value: ['method1', 'method2'],
                        action: 'object'
                    }]
                }]);

            expect(actual.testMethod).toBeASpy();
            expect(actual.testMethod.and.exec().objectProperty).toBeTypeOf('object');
            expect(actual.testMethod.and.exec().objectProperty.method1).toBeASpy();
            expect(actual.testMethod.and.exec().objectProperty.method2).toBeASpy();
        });

        it('should add value property to created object', function () {
            var actual = esj.createSpyObject([{
                    name: 'testMethod',
                    value: [{
                        name: 'valueProperty',
                        value: ['value1', 'value2'],
                        action: 'property'
                    }]
                }]);

            expect(actual.testMethod).toBeASpy();
            expect(actual.testMethod.and.exec().valueProperty).toBeTypeOf('array');
            expect(actual.testMethod.and.exec().valueProperty[0]).toBe('value1');
            expect(actual.testMethod.and.exec().valueProperty[1]).toBe('value2');
        });

        it('should create self returing spies when using fluent action', function () {
            var actual = esj.createSpyObject([
                {
                    name: 'spy1',
                    action: 'fluent'
                },
                {
                    name: 'spy2',
                    action: 'fluent'
                },
                {
                    name: 'spy3',
                    action: 'fluent'
                }
            ]);

            actual.spy1().spy2().spy3();

            expect(actual.spy1).toBeASpy();
            expect(actual.spy1.and.exec()).toBe(actual);
            expect(actual.spy1).toHaveBeenCalled();
            expect(actual.spy2).toHaveBeenCalled();
            expect(actual.spy3).toHaveBeenCalled();
        });
    });

    describe('createFluentSpyObject', function () {
        it('should return an object with three spies', function () {
            var actual = esj.createFluentSpyObject('baseName', ['spy1', 'spy2', 'spy3']);

            expect(actual.spy1).toBeASpy();
            expect(actual.spy2).toBeASpy();
            expect(actual.spy3).toBeASpy();
        });

        it('should create spies that return the root object', function () {
            var actual = esj.createFluentSpyObject('baseName', ['spy1', 'spy2', 'spy3']);

            expect(actual.spy1.and.exec()).toBe(actual);
            expect(actual.spy2.and.exec()).toBe(actual);
            expect(actual.spy3.and.exec()).toBe(actual);
        });

        it('should track calls to each created spy', function () {
            var actual = esj.createFluentSpyObject('baseName', ['spy1', 'spy2', 'spy3']);

            actual.spy3().spy2().spy1();

            expect(actual.spy1).toHaveBeenCalled();
            expect(actual.spy2).toHaveBeenCalled();
            expect(actual.spy3).toHaveBeenCalled();
        });
    });

    describe('getSpyValue', function () {
        it('should return three level deep spy value', function () {
            var target = esj.createSpyObject([{
                    name: 'level1',
                    value: [{
                        name: 'level2',
                        action: 'object',
                        value: [{
                            name: 'level3',
                            value: <any[]>['method1', 'method2', {
                                name: 'property1',
                                value: true,
                                action: 'property'
                            }]
                        }]
                    }]
                }]),
                actual = esj.getSpyReturn(target, 'level1.level2.level3');

            expect(actual).toBeTypeOf('object');
            expect(actual.method1).toBeASpy();
            expect(actual.method2).toBeASpy();
            expect(actual.property1).toBe(true);
        });
    });
});